// ======================================================================
// Copyright (C) 2013 Hell-Prototypes. / www.hellprototypes.com
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or (at
// your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// ======================================================================
#include "usb_thread.h"
#include <QDebug>

USB_Thread::USB_Thread(QObject *parent) :
    QThread(parent)
{
    m_usb_dev = NULL;
    run_flag = true;
}

bool USB_Thread::usb_connect_ctrl()
{
    if(m_usb_dev == NULL) {
        usb_init(); /* initialize the library */
        usb_find_busses(); /* find all busses */
        usb_find_devices(); /* find all connected devices */

        if (!(m_usb_dev = open_dev())) {
            //emit note_message("Can't opening USB device: " + (QString)usb_strerror());
            return false;
        } else {
            //qDebug("open_dev success: device %04X:%04X opened", MY_VID, MY_PID);
            if (usb_claim_interface(m_usb_dev, 0) < 0) {
                //emit note_message("error claiming interface :" + (QString)usb_strerror());
                usb_close(m_usb_dev);
                m_usb_dev = NULL;
                return false;
            } else {
                //qDebug("success: claim_interface");
            }

        }
    } else {
        usb_close(m_usb_dev);
        m_usb_dev = NULL;
    }
    return true;
}

/* open usb device */
usb_dev_handle* USB_Thread::open_dev(void)
{
    struct usb_bus *bus;
    struct usb_device *dev;

    for (bus = usb_get_busses(); bus; bus = bus->next) {
        for (dev = bus->devices; dev; dev = dev->next) {
            if ((dev->descriptor.idVendor == MY_VID) && (dev->descriptor.idProduct == MY_PID)) {
                return usb_open(dev);
            }
        }
    }
    return NULL;
}
/* usb send control command */
bool USB_Thread::send_ctrl_cmd(char *cmd_data, int cmd_len)
{
    if(m_usb_dev == NULL) {
        return false;
    }
    if( usb_control_msg(m_usb_dev, 0x40, 0xb0, 0x0000, 0x0000,
                        cmd_data, cmd_len, 100) < 0) {
        //qDebug("send_ctrl_cmd fail");
        return false;
    }
    return true;
}

bool USB_Thread::RECFG_FPGA()
{
    char cmd_buf[2];

    cmd_buf[0] = FPGA_RECFG_CMD;

    if( send_ctrl_cmd(cmd_buf, 1) ) {
        return true;
    }
    return false;
}

bool USB_Thread::RESET_FPGA()
{
    char cmd_buf[2];

    cmd_buf[0] = FPGA_RESET_CMD;

    if( send_ctrl_cmd(cmd_buf, 1) ) {
        return true;
    }
    return false;
}

bool USB_Thread::connect_usb()
{
    if(usb_connect_ctrl()) {
        if(RECFG_FPGA()) {
            if(RESET_FPGA()) {
                return true;
            }
        }
        //disconnect
        usb_close(m_usb_dev);
        m_usb_dev = NULL;
    }

    return false;
}

void USB_Thread::disconnect_usb()
{
    if(m_usb_dev != NULL) {
        if(this->isRunning()) {
            stop_data_read();
            this->wait(3*1000);
        }

        RESET_FPGA();
        usb_close(m_usb_dev);
        m_usb_dev = NULL;
    }
}

bool USB_Thread::set_sampling_divider(int divider)
{
    char cmd_buf[5];

    if((divider == 0) || (divider > 0xffff) || this->isRunning()) {
        return false;
    }

    if(!RESET_FPGA()) {
        return true;
    }

    cmd_buf[0] = FPGA_START_CMD;
    cmd_buf[1] = 0;
    cmd_buf[2] = 0;
    cmd_buf[3] = (char)((divider >> 8) & 0xff);
    cmd_buf[4] = (char)( divider       & 0xff);

    if( send_ctrl_cmd(cmd_buf, 5) ) {
        return true;
    }

    return false;
}

void USB_Thread::stop_data_read()
{
    if(this->isRunning()) {
        run_flag = false;
    }
}

void USB_Thread::fifo_take(char *take_buffer, int len)
{
   //m_sys_clock.restart();
    mutex.lock();
    int size = m_fifo_array.size();
    if(len > size) {
        len = size;
    }
    memcpy(take_buffer, m_fifo_array.constData(), len);
    if(size - len > 4*1024*1024) {
        m_fifo_array.clear();
        emit note_message("Processing speed was slow, discard some data");
    } else {
        m_fifo_array.remove(0, len);
    }
    mutex.unlock();
    //qDebug("fifo_take m_sys_clock.elapsed() = %f, size = %d", m_sys_clock.elapsed(), m_fifo_array.size());
}

void USB_Thread::run()
{
    char read_buffer[USB_BULK_READ_LEN];
    int  read_len;

    while(run_flag) {
        read_len = usb_bulk_read(m_usb_dev, USB_BULK_EP_IN, read_buffer, USB_BULK_READ_LEN, 3000);
        if(read_len < 0) {
            //qDebug() << "usb_bulk_read error : " << read_len << " : " << usb_strerror();
            emit usb_error(read_len);
            break;
        }

        //m_sys_clock.restart();
        mutex.lock();
        m_fifo_array.append(read_buffer, read_len);
        mutex.unlock();
        //qDebug("3 UT m_sys_clock.elapsed() = %f", m_sys_clock.elapsed());
        emit rle_data_ready(read_len);
    }
    run_flag = true;
}
